package autotd;
import autotd.server.*;



import java.util.*;
import java.awt.Point;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class TDSimulator {
	public static final String DEFAULT_SETTINGS_FILE = "src/autotd/files/testgame.atd";

	public int maximumTowerStackSize = 10;
	
	private ArrayList<AdvancedSimulatorListener> advancedListeners;

	public Player player;
	public GameMap map;
	public MonsterGenerator monsterGenerator;
	public Map<String,TowerType> towerTypes;
	public Map<String,MonsterType> monsterTypes;
	
	public Set<Monster> monsters;
	public Set<Tower> towers;
	
	public int turn = 0;
	
	private TDSimulator(GameMap gm, MonsterGenerator mg, List<TowerType> tt, List<MonsterType> mt, Player play) {
		map = gm;
		monsterGenerator = mg;
		monsters = new HashSet<Monster>();
		towers = new HashSet<Tower>();
		towerTypes = new HashMap<String, TowerType>();
		for (TowerType ttelt : tt) {
			towerTypes.put(ttelt.Name, ttelt);
		}
		monsterTypes = new HashMap<String, MonsterType>();
		for (MonsterType mtelt : mt) {
			monsterTypes.put(mtelt.Name, mtelt);
		}
		
		player = play;
		advancedListeners = new ArrayList<AdvancedSimulatorListener>();
	}

	public void addAdvancedListener(AdvancedSimulatorListener al) {
		advancedListeners.add(al);
	}

	public void cullDeadMonsters() {
		// check for dead monsters
		for (Monster m: new HashSet<Monster>(monsters)) {
			if (m.isDead()) {
				monsters.remove(m);
				map.remove(m);
				player.setMoney(player.getMoney() + m.Type.Reward);
				for (AdvancedSimulatorListener al: advancedListeners) {
					al.playerChange(player.getLives(), player.getMoney());
				}
			}
		}
	}


	public boolean tick() {
		if (!player.isAlive()) {
			return false;
		}

		turn++;
		for (AdvancedSimulatorListener al: advancedListeners) {
			al.turnChangedTo(turn);
		}

		cullDeadMonsters();

		// add newly spawned monsters
		Monster[] spawned = monsterGenerator.tick();
		for (int pos = 0; pos < spawned.length; pos++) {
			monsters.add(spawned[pos]);
			for (AdvancedSimulatorListener al: advancedListeners) {
				al.monsterCreated(spawned[pos].id, spawned[pos].Type.Name, spawned[pos].Level, spawned[pos].pos.x, spawned[pos].pos.y);
			}
		}
		
		// move all monsters
		for (Monster m: new HashSet<Monster>(monsters)) {
			Point lastPos = m.pos;
			m.tick();
			if (m.ready()) {
				m.act();
				map.moveForward(m);
				// check for monsters that have completed the path!
				if (m.isPassed()) {
					player.hit(1);
					map.remove(m);
					monsters.remove(m);
					for (AdvancedSimulatorListener al: advancedListeners) {
						al.monsterCompletedPath(m.id);
						al.playerChange(player.getLives(), player.getMoney());
					}
				} else {
					for (AdvancedSimulatorListener al: advancedListeners) {
						al.monsterMoved(m.id, lastPos.x, lastPos.y, m.pos.x, m.pos.y);
					}
				}
			}
		}

		
		// tick towers
		for (Tower t: towers) {
			t.tick();
		}
		
		return true;
	}




	public boolean placeTower(TowerType type, int x, int y) {
		Point tp = new Point(x, y);
		if ((player.getMoney() < type.Cost) || !map.isGrass(tp)) {
			return false;
		}
		if (map.getTowerStack(tp).size() >= maximumTowerStackSize) {
			return false;
		}
		int height = map.getHeight(x, y);
		Tower newt = new Tower(type, height);	
		map.add(newt, tp);
		player.setMoney(player.getMoney() - type.Cost);
		towers.add(newt);
		for (AdvancedSimulatorListener al: advancedListeners) {
			al.towerPlaced(newt.id, newt.Type.Name, x, y, newt.height);
			al.playerChange(player.getLives(), player.getMoney());
		}
		
		return true;
	}
	
	
	/** @return int The final amount of damage applied. */
	private static int calculateDamage(Monster m, int baseDamage, int tHeight) {
		double finalArmour = 0.0;
		if (tHeight == 0) {
			finalArmour = m.Type.Armour * m.Type.Height;
		} else {
			finalArmour = m.Type.Armour * m.Type.Height / (double)tHeight;
		}
		return Math.max((int)(baseDamage - finalArmour + 0.5), 0);
	}



	/** @return true if the shot actually hits the target */
	private boolean shoot(Tower shooter, Monster target) {
		if (!shooter.ready() || !GameMap.inRange(shooter.pos, target.pos, shooter.Type.Range) || target.isDead()) {
			return false;
		}
		shooter.act();
		for (AdvancedSimulatorListener al: advancedListeners) {
			al.towerFired(shooter.id, target.id);
		}
		int dmg = calculateDamage(target, shooter.Type.Power, shooter.height);
		if (dmg > 0) {
			target.health = Math.max(target.health - dmg, 0);
		}
		// Splash Damage
		for (Monster m : monsters) {
			if (m == target) {
				continue; // no double-splash
			}
			if (GameMap.inRange(target.pos, m.pos, shooter.Type.Splash)) {
				int sdmg = calculateDamage(m, shooter.Type.Power, shooter.height);
				if (sdmg > 0) {
					m.health = Math.max(m.health - sdmg, 0);
				}
			}
		}
		return true;
	}

	/** @return True if at least one of the towers hit the monster */
	public boolean shootMonster(Point p, Monster target) {
		Map<Integer, Integer> oldHealth = new HashMap<Integer, Integer>();
		for (Monster m : monsters) {
			oldHealth.put(m.id, m.health);
		}
		boolean hit = false;
		for (Tower shooter: map.getTowerStack(p)) {
			boolean newhit = shoot(shooter, target);
			hit = hit || newhit;
		}
		if (!hit) {
			return false;
		}
		for (Monster m : monsters) {
			if (m.health != oldHealth.get(m.id)) {
				for (AdvancedSimulatorListener al: advancedListeners) {
					al.monsterInjured(m.id, m.health);
				}
			}
		}
		
		return true;
	}



	public GameMap getMap() {
		return map;
	}
	
	public Player getPlayer() {
		return player;
	}
	
	public Set<Monster> getActiveMonsters() {
		return new HashSet<Monster>(monsters);
	}
	
	public Set<Tower> getPlacedTowers() {
		return new HashSet<Tower>(towers);
	}
	
	public MonsterGenerator getMonsterGenerator() {
		return monsterGenerator;
	}
	
	public int getTurnNumber() {
		return turn;
	}

	public Tower lookupTower(int id){
		for(Tower t: towers){
			if(t.id == id){
				return t;
			}
		}
		return null;
	}


	
	public Monster lookupMonster(int id){
		for(Monster m: monsters){
			if(m.id == id){
				return m;
			}
		}
		return null;
	}
	public List<MonsterType> getMonsterTypes() {
		return new ArrayList<MonsterType>(monsterTypes.values());
	}

	public Map<String, MonsterType> getMonsterTypesMap() {
		return monsterTypes;
	}

	public List<TowerType> getTowerTypes() {
		return new ArrayList<TowerType>(towerTypes.values());
	}

	public Map<String, TowerType> getTowerTypesMap() {
		return towerTypes;
	}
	
	public TowerType getTowerType(String name) {
		return towerTypes.get(name);
	}
	
	public MonsterType getMonsterType(String name) {
		return monsterTypes.get(name);
	}
	
	public Set<Tower> getTowers() {
		return new HashSet<Tower>(towers);
	}
	




	public static TDSimulator loadSettings(String fname) throws Exception {
		File f = new File(fname);
		BufferedReader br = new BufferedReader(new FileReader(f));
		File mapfile = new File(f.getParent(),br.readLine());
		File generatorfile = new File(f.getParent(),br.readLine());
		File towerfile = new File(f.getParent(),br.readLine());
		File monsterfile = new File(f.getParent(),br.readLine());
		String[] playerTokens = br.readLine().split(" ");
		String maxTowerStack = br.readLine();
		
		List<TowerType> towers = TowerType.parseFile(towerfile);
		List<MonsterType> monsters = MonsterType.parseFile(monsterfile);
		GameMap map = GameMap.parseFile(mapfile);
		MonsterGenerator generator = MonsterGenerator.parseFile(map,monsters, generatorfile);
		Player play = new Player(Integer.parseInt(playerTokens[0]), Integer.parseInt(playerTokens[1]));
		TDSimulator sim = new TDSimulator(map,generator,towers,monsters,play);
		sim.maximumTowerStackSize = Integer.parseInt(maxTowerStack);
		return sim;
	}


	public static TDSimulator pathSearchLoadSettings(String path) {
		while (path.length() > 0) {
			try {
				TDSimulator sim = loadSettings(path);
				return sim;
			} catch(Exception e) {
			}
			if (path.indexOf("/") > 0) {
				path = path.substring(path.indexOf("/")+1, path.length());
			} else if (path.indexOf("\\") > 0) {
				path = path.substring(path.indexOf("\\")+1, path.length());
			} else {
				path = "";
			}
		}
		return null;
	}
	
	public static TDSimulator getDefaultSimulator() {
		return pathSearchLoadSettings(DEFAULT_SETTINGS_FILE);
	}
}









